package com.aaa.framework.redis;

import com.ctg.itrdc.cache.pool.CtgJedisPool;
import com.ctg.itrdc.cache.pool.CtgJedisPoolConfig;
import com.ctg.itrdc.cache.pool.ProxyJedis;
import com.aaa.common.utils.RedisJsonUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConverters;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

import java.util.*;

@Component
@ConditionalOnProperty(name = "app.config.cache.ctg-cache", havingValue = "true")
public class RedisCtgService implements InitializingBean, DisposableBean, com.aaa.framework.redis.RedisCtg {
    private static final Logger logger = LoggerFactory.getLogger(JedisConnectionFactory.class);
    private RedisProperties redisProperties;
    private int database;
    private CtgJedisPool jedisPool;

    public RedisCtgService(RedisProperties redisProperties) {
        this.redisProperties = redisProperties;
        this.database = redisProperties.getDatabase();
    }

    public void afterPropertiesSet() {
        this.initPool();
    }

    protected void initPool() {
        List<HostAndPort> nodes = new ArrayList<>();
        String[] hosts = redisProperties.getHost().split(",");
        for (String host : hosts) {
            String[] ipPortPair = host.split(":");
            nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
        }
        // 线程池配置
        GenericObjectPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(redisProperties.getJedis().getPool().getMaxIdle());
        poolConfig.setMaxTotal(redisProperties.getJedis().getPool().getMaxActive());
        poolConfig.setMinIdle(redisProperties.getJedis().getPool().getMinIdle());
        poolConfig.setMaxWaitMillis(redisProperties.getJedis().getPool().getMaxWait().toMillis());
        CtgJedisPoolConfig config = new CtgJedisPoolConfig(nodes);
        // 分组对应的桶位
        config.setDatabase(redisProperties.getDatabase())
                // “用户#密码”
                .setPassword(redisProperties.getPassword())
                // 线程池配置
                .setPoolConfig(poolConfig)
                // 后台监控执行周期，毫秒
                .setPeriod(3000)
                // 后台监控ping命令超时时间,毫秒
                .setMonitorTimeout(100)
                // 操作超时时间，单位毫秒，默认2秒
                .setSoTimeout((int) redisProperties.getTimeout().toMillis())
                // 连接超时时间，单位毫秒，默认2秒
                .setConnectionTimeout((int) redisProperties.getConnectTimeout().toMillis())
                // 监控连续出错几次，将接入机节点判断为失效，默认3次
                .setMonitorErrorNum(3)
                // 是否开启后台监控日志，默认开启
                .setMonitorLog(true);
        try {
            this.jedisPool = new CtgJedisPool(config);
        } catch (Throwable e) {

            throw new RuntimeException("ctgcache pool init failure!", e);
        }
        logger.info("ctgcache pool init finish!");
    }

    private <T> T execute(RedisCallback<T> action) {
        ProxyJedis jedis = null;
        try {
            jedis = this.jedisPool.getResource();
            jedis.select(this.database);
            return action.doInRedis(jedis);
        } catch (Exception e) {
            logger.error("redis execute failure. retry!", e);
            try {
                if (jedis != null) {
                    jedis.close();
                }
                Thread.sleep(1000);
                jedis = this.jedisPool.getResource();
                jedis.select(this.database);
                return action.doInRedis(jedis);
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }

    @Override
    public boolean expire(String key, long time) {
        return this.execute((jedis) -> {
            jedis.expire(key, (int) time);
            return true;
        });
    }

    @Override
    public boolean hasKey(String key) {
        return this.execute((jedis) -> jedis.exists(key));
    }

    @Override
    public Set<String> hKeys(String key) {
        return this.execute((jedis) -> jedis.hkeys(key));
    }

    @Override
    public boolean del(String... key) {
        return this.execute((jedis) -> {
            jedis.del(key);
            return true;
        });
    }

    @Override
    public boolean del(List<String> keys) {
        return this.execute((jedis) -> {
            // jedis.del((String[])keys.toArray());
            jedis.del(keys.toArray(new String[0]));
            return true;
        });
    }

    @Override
    public long delMore(List<String> keys) {
        return this.execute((jedis) -> {
            return jedis.del(keys.toArray(new String[0]));
        });
    }

    @Override
    public <T> T get(String key) {
        return this.execute((jedis) -> {
            String jsonStr = jedis.get(key);
            return RedisJsonUtils.fromJson(jsonStr);
        });
    }

    @Override
    public boolean set(String key, Object value) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            if ("OK".equals(jedis.set(key, jsonStr))) {
                return true;
            }
            return false;
        });
    }

    @Override
    public boolean set(String key, Object value, long time) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            if ("OK".equals(jedis.setex(key, (int) time, jsonStr))) {
                return true;
            }
            return false;
        });
    }

    @Override
    public boolean setnx(String key, String value, long time) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            if ("OK".equals(jedis.setpxnx(key, jsonStr, time))) {
                return true;
            }
            return false;
        });
    }

    @Override
    public long incr(String key, long delta) {
        return this.execute((jedis) -> {
            return jedis.incrBy(key, delta);
        });
    }

    @Override
    public long decr(String key, long delta) {
        return this.execute((jedis) -> {
            return jedis.decrBy(key, delta);
        });
    }

    @Override
    public <T> T hget(String key, String item) {
        return this.execute((jedis) -> {
            String jsonStr = jedis.hget(key, item);
            return RedisJsonUtils.fromJson(jsonStr);
        });
    }

    @Override
    public <K, V> Map<K, V> hmget(String key) {
        return this.execute((jedis) -> {
            Map<String, String> entries = jedis.hgetAll(key);
            Map map = new LinkedHashMap(entries.size());
            Iterator iterator = entries.entrySet().iterator();

            while (iterator.hasNext()) {
                Map.Entry<String, String> entry = (Map.Entry) iterator.next();
                map.put(entry.getKey(), RedisJsonUtils.fromJson(entry.getValue()));
            }
            return map;
        });
    }

    @Override
    public <T> List<T> hmget(String key, String... fields) {
        return this.execute((jedis) -> {
            if (fields.length <= 0) {
                return Collections.emptyList();
            }

            List<String> hmget = jedis.hmget(key, fields);
            List result = new ArrayList();
            hmget.forEach(v -> {
                result.add(RedisJsonUtils.fromJson(v));
            });
            return result;
        });
    }

    @Override
    public boolean hmset(String key, Map<String, Object> entries) {
        return this.execute((jedis) -> {
            Map<String, String> map = new LinkedHashMap(entries.size());
            Iterator iterator = entries.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry) iterator.next();
                map.put(entry.getKey(), RedisJsonUtils.toJson(entry.getValue()));
            }
            jedis.hmset(key, map);
            return true;
        });
    }

    @Override
    public boolean hmset(String key, Map<String, Object> entries, long time) {
        return this.execute((jedis) -> {
            Map<String, String> map = new LinkedHashMap(entries.size());
            Iterator iterator = entries.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, String> entry = (Map.Entry) iterator.next();
                map.put(entry.getKey(), RedisJsonUtils.fromJson(entry.getValue()));
            }
            jedis.hmset(key, map);
            jedis.expire(key, (int) time);
            return true;
        });
    }

    @Override
    public boolean hset(String key, String item, Object value) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            jedis.hset(key, item, jsonStr);
            return true;
        });
    }

    @Override
    public boolean hset(String key, String item, Object value, long time) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            jedis.hset(key, item, jsonStr);
            jedis.expire(key, (int) time);
            return true;
        });
    }

    @Override
    public long sadd(String key, String... members) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(members);
            return jedis.sadd(key, jsonStr);
        });
    }

    @Override
    public <T> Set<T> smembers(String key) {
        return this.execute((jedis) -> {
            Set<String> smembers = jedis.smembers(key);
            Set result = new HashSet();
            smembers.forEach(v -> {
                result.add(RedisJsonUtils.fromJson(v));
            });
            return result;
        });
    }

    @Override
    public boolean hdel(String key, String... item) {
        return this.execute((jedis) -> {
            jedis.hdel(key, item);
            return true;
        });
    }

    @Override
    public boolean hHasKey(String key, String item) {
        return this.execute((jedis) -> {
            return jedis.hexists(key, item);
        });
    }

    @Override
    public double hincr(String key, String item, double by) {
        return (double) this.execute((jedis) -> {
            return jedis.hincrBy(key, item, (long) by);
        });
    }

    @Override
    public double hdecr(String key, String item, double by) {
        return (double) this.execute((jedis) -> {
            return jedis.hincrBy(key, item, (long) -by);
        });
    }

    @Override
    public <T> Set<T> sGet(String key) {
        return this.execute((jedis) -> {
            Set<String> members = jedis.smembers(key);
            Set sget = new LinkedHashSet<>();
            members.forEach(v -> {
                sget.add(RedisJsonUtils.toJson(v));
            });
            return sget;
        });
    }

    @Override
    public boolean sHasKey(String key, Object value) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            return jedis.sismember(key, jsonStr);
        });
    }

    @Override
    public long sSet(String key, Object... values) {
        return this.execute((jedis) -> {
            List<String> sadd = new ArrayList<>();
            for (Object value : values) {
                sadd.add(RedisJsonUtils.toJson(value));
            }
            Long ret = jedis.sadd(key, (String[]) sadd.toArray());
            return ret;
        });
    }

    @Override
    public long sSetAndTime(String key, long time, Object... values) {
        return this.execute((jedis) -> {
            List<String> sadd = new ArrayList<>();
            for (Object value : values) {
                sadd.add(RedisJsonUtils.toJson(value));
            }
            Long ret = jedis.sadd(key, (String[]) sadd.toArray());
            jedis.expire(key, (int) time);
            return ret;
        });
    }

    @Override
    public long sGetSetSize(String key) {
        return this.execute((jedis) -> {
            return jedis.scard(key);
        });
    }

    @Override
    public long setRemove(String key, Object... values) {
        return this.execute((jedis) -> {
            List<String> srem = new ArrayList<>();
            for (Object value : values) {
                srem.add(RedisJsonUtils.toJson(value));
            }
            return jedis.srem(key, (String[]) srem.toArray());
        });
    }

    @Override
    public <T> List<T> lGet(String key, long start, long end) {
        return this.execute((jedis) -> {
            List<String> lrange = jedis.lrange(key, start, end);
            List<T> lget = new ArrayList<>();
            lrange.forEach(v -> {
                lget.add(RedisJsonUtils.fromJson(v));
            });
            return lget;
        });
    }

    @Override
    public long lGetListSize(String key) {
        return this.execute((jedis) -> {
            return jedis.llen(key);
        });
    }

    @Override
    public <T> T lGetIndex(String key, long index) {
        return this.execute((jedis) -> {
            String jsonStr = jedis.lindex(key, index);
            return RedisJsonUtils.fromJson(jsonStr);
        });
    }

    @Override
    public boolean lSet(String key, Object value) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            jedis.lpush(key, jsonStr);
            return true;
        });
    }

    @Override
    public boolean lSet(String key, Object value, long time) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            jedis.lpush(key, jsonStr);
            jedis.expire(key, (int) time);
            return true;
        });
    }

    @Override
    public boolean lSet(String key, List<?> value) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            List<String> values = new ArrayList<>(value.size());
            value.forEach(v -> {
                values.add(RedisJsonUtils.toJson(v));
            });
            jedis.lpush(key, values.toArray(new String[0]));
            //jedis.lpush(key, (String[])values.toArray());
            return true;
        });
    }

    @Override
    public boolean lSet(String key, List<Object> value, long time) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            List<String> values = new ArrayList<>(value.size());
            value.forEach(v -> {
                values.add(RedisJsonUtils.toJson(v));
            });
            jedis.lpush(key, (String[]) values.toArray());
            jedis.expire(key, (int) time);
            return true;
        });
    }

    @Override
    public boolean lUpdateIndex(String key, long index, Object value) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            jedis.lset(key, index, jsonStr);
            return true;
        });
    }

    @Override
    public <T> List<T> lrange(String key, long start, long stop) {
        return this.execute((jedis) -> {
            List<String> lrange = jedis.lrange(key, start, stop);
            List result = new LinkedList();
            lrange.forEach(v -> {
                result.add(RedisJsonUtils.fromJson(v));
            });
            return result;
        });
    }

    @Override
    public Long lpush(String key, Object value) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            return jedis.lpush(key, jsonStr);
        });
    }

    @Override
    public Long rpush(String key, Object value) {
        return this.execute((jedis) -> {
            String jsonStr = RedisJsonUtils.toJson(value);
            return jedis.rpush(key, jsonStr);
        });
    }

    @Override
    public <T> T rpop(String key) {
        return this.execute((jedis) -> {
            String jsonStr = jedis.rpop(key);
            return RedisJsonUtils.fromJson(jsonStr);
        });
    }

    @Override
    public long lRemove(String key, long count, Object value) {
        return this.execute((jedis) -> {
            return jedis.lrem(key, count, RedisJsonUtils.toJson(value));
        });
    }

    @Override
    public <T> T rightPopAndLeftPush(String key, String destKey) {
        return this.execute((jedis) -> {
            String value = jedis.rpop(key);
            if (value != null) {
                jedis.lpush(destKey, value);
            }
            return RedisJsonUtils.fromJson(value);
        });
    }

    @Override
    public List<String> keys(String pattern) {
        return this.execute((jedis) -> {
            List<String> keys = new ArrayList<>();
            ScanParams params = new ScanParams();
            params.count(50000);
            params.match(pattern);

            String cursor = ScanParams.SCAN_POINTER_START;
            do {
                ScanResult<String> result = jedis.scan(cursor, params);
                cursor = String.valueOf(result.getCursor());
                keys.addAll(result.getResult());
            }
            while (cursor != null && !String.valueOf(cursor).equals(ScanParams.SCAN_POINTER_START));
            return keys;
        });
    }

    @Override
    public DataType type(String key) {
        return this.execute((jedis) -> {
            return JedisConverters.toDataType(jedis.type(key));
        });
    }

    @Override
    public <T> Set<T> szGet(String key, long start, long end) {
        return this.execute((jedis) -> {
            Set<String> zrange = jedis.zrange(key, start, end);
            Set szget = new LinkedHashSet();
            zrange.forEach(v -> {
                szget.add(RedisJsonUtils.fromJson(v));
            });
            return szget;
        });
    }

    @Override
    public boolean szSet(String key, Object item, double score) {
        return this.execute((jedis) -> {
            String value = RedisJsonUtils.toJson(item);
            Long zadd = jedis.zadd(key, score, value);
            return true;
        });
    }

    @Override
    public long ttl(String key) {
        return this.execute((jedis) -> jedis.ttl(key));
    }

    @Override
    public void destroy() throws Exception {
        jedisPool.close();
    }

    public interface RedisCallback<T> {
        @Nullable
        T doInRedis(ProxyJedis jedis);
    }
}
